import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Stack;

/**
 * 
 * Moises Florez and Peter Matano
 *
 */
public class OS implements OperatingSystem {
	private Hardware simHW;
	private DiskEntity dEnt;
	private ProgramEntity proEnt;	
	int ttyData = 1;
	
	boolean terminalInUse;
	private Stack<Integer> terminalUseStack;
		
	int putSlotSenderProcessID;
	
	boolean readyRead;
	boolean readyWrite;
	
	private List<ConnectionDetails> connectionDetailList;
		
	private RoundRobinScheduler processScheduler;
	private List<Process> processQueue;
	
	private int terminalDataStartAddress = 0;
	private int numberOfCharToRead = 0;
	
	private int countdown = 90000;
	private int clock = countdown;
	private int serviceTime = 2;
	
	private MemoryManager memoryManager;	
		
	public OS(Hardware hw) {
		simHW = hw; // Set simulator hardware.
		proEnt = new ProgramEntity();
		dEnt = new DiskEntity();
		terminalUseStack = new Stack<Integer>();
		processQueue = new ArrayList<Process>();
		processScheduler = new RoundRobinScheduler(processQueue, clock);		
		connectionDetailList = new ArrayList<ConnectionDetails>();		
		memoryManager = new MemoryManager();		
	}
		
	/**
	 * Halt the OS
	 */
	private void haltOS(){
		simHW.store(Hardware.Address.haltRegister, 2);
	}
	
	/**
	 * Set HW status OK. This start the actual execution command in systemCalls
	 */
	private void setHardwareStatusOK() {
		this.simHW.store(Hardware.Address.systemBase, Hardware.Status.ok);
	}
	
	/**
	 * Store the connection ID to an undefined value.
	 */
	private void setUndefinedConnectionId() {		
		this.simHW.store(Hardware.Address.systemBase + 1, -1);
	}
	
	/**
	 * Idle start program.
	 */
	private void setIdleStart() {
		this.simHW.store(Hardware.Address.baseRegister, Hardware.Address.idleStart); // Lowest legal address accessible to the current running process.
		this.simHW.store(Hardware.Address.topRegister, Hardware.Address.idleEnd); // One more than the highest legal address.
		this.simHW.store(Hardware.Address.PCRegister, Hardware.Address.idleStart);//Set PCRegister to prevent illegal instruction interrupt
	}
	
	/*
	 * Set count down-timer
	 */
	private void setCountdownStart(int countdown) {
		simHW.store(Hardware.Address.countdownRegister, countdown);	
	}
	
	/**
	 * Reset count-down.
	 */
	private void resetCountdown() {
		this.simHW.store(Hardware.Address.countdownRegister, -1); 
	}
	
	/**
	 * Get arrival time from the clockRetister.
	 * @return
	 */
	private int getArrivalTime() {
		int arrivalTime = this.simHW.fetch(Hardware.Address.clockRegister);		
		return arrivalTime;
	}
	
	/**
	 * OS Interrupt handler 
	 */
	@Override
	public void interrupt(Hardware.Interrupt it) {
		switch (it) {
		case illegalInstruction:
			println("Interrupt: illegalInstruction");
			this.haltOS();
			break;	
		case reboot:
			println("Interrupt: reboot");
			// Load the disk to primary store one block at the time.
						
			int userBaseStartAddress = Hardware.Address.userBase;		
			/*
			 * Start tracking read information.
			 */
			int block0 = memoryManager.getBlockIndex();
			this.memoryManager.setBlockStartAddress(userBaseStartAddress);
												
			/*
			 * Read the index block into primary store starting at userBase.
			 */
			readDiskBlockIntoPrimaryStore(block0, userBaseStartAddress);			
			/*
			 * Start idle
			 */
			this.setIdleStart();			
			break;
		case systemCall:
			int sysCallVal = simHW.fetch(Hardware.Address.systemBase);
			println("Interrupt: systemCall(" + sysCallVal + ")");
			operatingSystemCall(sysCallVal);
			break;		
		case invalidAddress:
			println("Interrupt: invalidAddress");
			haltOS();
			break;			
		case disk:
			println("Interrupt: disk");	
			/*
			 * This will execute only once, the first time the OS is booted.  
			 */
			if (memoryManager.isOperatingSystemInitialized() == false) {
				this.initializeOperatingSystem();
			} else if (memoryManager.isOperatingSystemInitialized() == true) {				
				
				if (readyRead) {
					readyRead = false;
					println("Ready to read user data...");					
					println("");	
					
				} else if (readyWrite) {
					readyWrite = false;
					println("Ready to write user data...");									
					println("");					
				}
			}
			
			/*
			 * This is what will trigger the call.
			 */
			this.setHardwareStatusOK();
			break;
		case terminal:
			println("Interrupt: terminal");					
								
			int data = this.simHW.fetch(Hardware.Address.terminalDataRegister);
			println("Terminal Data: " + data);	
					
			int readToAddress = this.simHW.fetch(Hardware.Address.systemBase + 2); // Word 2
			println("executeDeviceReadCall->Terminal (readToAddress): Word 2: " + readToAddress);
		
			int nValue = this.simHW.fetch(Hardware.Address.systemBase + 3); // Word 3
			numberOfCharToRead = nValue;
			println("executeDeviceReadCall->Terminal (nValue): Word 3: " + nValue);
			
			int status = this.simHW.fetch(Hardware.Address.terminalStatusRegister);
			if(status == Hardware.Status.ok)
			{
				println("Terminal: Hardware.Status.ok");
				if (numberOfCharToRead > 0){
					println("terminalDataStartAddress: " + terminalDataStartAddress);
					int terminalData = this.simHW.fetch(terminalDataStartAddress);
					println("terminalData: " + terminalData);
					ttyData = this.simHW.fetch(Hardware.Address.terminalDataRegister); // Copy the data from the tty data registry
										
					this.simHW.store(terminalDataStartAddress, ttyData);
					this.simHW.store(Hardware.Address.terminalDataRegister, ttyData);
					
					println("numberOfChrToRead: " + numberOfCharToRead);					
				}				
			} else if (status == Hardware.Status.badCommand)
			{
				this.setDeviceStatus(Hardware.Status.badCommand);
			}			
			break;
		case countdown:
			int cDownReg = simHW.fetch(Hardware.Address.countdownRegister);
			if (cDownReg == 0) { 				
				this.setCountdownStart(countdown);								
			}
			executeProcessScheduling();			
			break;
		}
	}	
	
	/**
	 * Operating system calls.
	 * @param sysCall
	 */
	public void operatingSystemCall(int sysCall) {		
		switch (sysCall) {
		case SystemCall.exec:
			println("SystemCall: exec");
			this.osCall_Exe();			
			break;
		case SystemCall.exit:
			println("SystemCall: exit");			
			this.haltOS();
			break;		
		case SystemCall.getSlot:			
			println("SystemCall: getSlot");
			this.osCall_GetSlot();		
			break;		
		case SystemCall.putSlot:
			println("SystemCall: putSlot");				
			this.osCall_PutSlot();		
			break;		
		case SystemCall.yield:
			println("SystemCall: yield");			
			this.osCall_Yield();
			break;
		case SystemCall.open:
			println("SystemCall: open");
			this.osCall_Open();
			break;
		case SystemCall.read:
			println("SystemCall: read");
			this.osCall_Read();			
			break;
		case SystemCall.write:
			println("SystemCall: write");
			this.osCall_Write();			
			break;
		case SystemCall.close:
			println("SystemCall: close");
			this.osCall_Close();
			break;
		}
	}
		
	/**
	 * OS Call Execute
	 */
	public void osCall_Exe() {			
		/*
		 * The calling process requests that a new process start executing. 
			Register 1 contains the word number of the batch-disk index block 
			for the program associated with the new process. After 
			the exec system call returns, the caller's register 0 contains the system call status
			and register 1 contains the new process's id if the exec was successful; otherwise the contents of register 1 is undefined.
			  
			The contents of registers 0 and 2 through 10 in the new process (the child process) are identical to the contents of the 
			associated registers in the process that issued the exec (the parent process). Register 1 in the child process contains the parent process's id.
		 */
		/*
		 * Get the initial program ID.
		 */
		int progamID = this.simHW.fetch(Hardware.Address.systemBase + 1); // Get register 1.
		println("Program ID Before Execute:::::" + progamID);
		/*
		 * Set the program using program ID.
		 */
		this.processProgramExeRequest(progamID);
		/*
		 * Set Idle start prevents illegal instruction error.
		 */
		this.setIdleStart();
		/*
		 * Set the hardware status to OK to trigger the call.
		 */
		this.setHardwareStatusOK();
		/*
		 * Get the status.
		 */
		int processStatus = this.simHW.fetch(Hardware.Address.systemBase);
		println("processStatus:: " + processStatus);
		if (processStatus == Hardware.Status.ok){
			int arrTime = this.getArrivalTime();
			/*
			 * Get the next process ID.
			 */
			int processID = this.processQueue.size();
			putSlotSenderProcessID =  processID;
			println("Process ID After Execute:::::" + processID);
			Process p = new Process(processID, arrTime, serviceTime);
			/*
			 * Store the process ID (next index) calculated above.
			 */
			this.simHW.store(Hardware.Address.systemBase + 1, processID);
			p.setParentProgramID(progamID);
			/*
			 * Add the process to the process queue.
			 */
			processQueue.add(p);						
			println("Execute::Hardware.Status.ok:::(" + processStatus + ")");
		} else if (processStatus == Hardware.Status.badPid){
			this.setDeviceStatus(Hardware.Status.badPid);
		}  else if (processStatus == Hardware.Status.noResource){
			this.setDeviceStatus(Hardware.Status.noResource);
		}
	}
	
	/**
	 * OS Call GetSlot
	 */
	public void osCall_GetSlot() {						
		/*
		 * Trigger the call.
		 */
		this.setHardwareStatusOK();
		
		int hardwareStatus = this.simHW.fetch(Hardware.Address.systemBase);
		if (hardwareStatus == Hardware.Status.ok){
			Process p = this.processQueue.get(putSlotSenderProcessID);
			int slotValue = p.getSlotValue();
			this.simHW.store(Hardware.Address.systemBase + 1, slotValue);
			this.simHW.store(Hardware.Address.systemBase + 2, putSlotSenderProcessID);
			this.simHW.store(Hardware.Address.systemBase, Hardware.Status.ok);
		}			
	}	
	
	/**
	 * OS Call PutSlot
	 */
	public void osCall_PutSlot() {
		/*
		 * Get receiver id at Word 1.
		 */
		int putSlotReceiverID = this.simHW.fetch(Hardware.Address.systemBase + 1);
		/*
		 * Slot value Word 2
		 */
		int putSlotSlotValue = this.simHW.fetch(Hardware.Address.systemBase + 2);
		/*
		 * This will trigger the call.
		 */
		this.setHardwareStatusOK();
		
		/*
		 * Get the hardwares status after the call for use in the 
		 */
		int putSlotHardwareStatus = this.simHW.fetch(Hardware.Address.systemBase);
		
		if (putSlotHardwareStatus == Hardware.Status.ok) {
			Process p = this.processQueue.get(putSlotReceiverID);
			p.setSlotValue(putSlotSlotValue);
			println("receiverID??:: " + putSlotReceiverID);
			processQueue.set(putSlotReceiverID, p);			
		} else if (putSlotHardwareStatus == Hardware.Status.badPid) {
			this.setDeviceStatus(Hardware.Status.badPid);	
		} else if (putSlotHardwareStatus == Hardware.Status.noResource) {
			this.setDeviceStatus(Hardware.Status.noResource);
		}
	}
	
	/**
	 * OS Call Yield
	 */
	public void osCall_Yield() {
		this.setHardwareStatusOK();
	}
	
	/**
	 * OS Call Open
	 */
	public void osCall_Open() {
		int openDeviceStatus = this.simHW.fetch(Hardware.Address.systemBase);
		println("Current Device Status: " + openDeviceStatus);
		int deviceType = this.simHW.fetch(Hardware.Address.systemBase + 1); // Word 1 (1 is drive, 3 is terminal)

		if ((deviceType == Hardware.Terminal.device || deviceType == Hardware.Disk.device)) {
			if (deviceType == Hardware.Disk.device){					
				/*
				 * Triggers the call.
				 */
				this.setHardwareStatusOK();																	
				int hwStatus = this.simHW.fetch(Hardware.Address.systemBase);					
				if (hwStatus == Hardware.Status.ok) {
					int getDeviceStatus = this.simHW.fetch(Hardware.Address.systemBase);
					/*
					 *Device is OK
					 */
					if (getDeviceStatus == Hardware.Status.ok) { 						
						ConnectionDetails cDetail = new ConnectionDetails();
						int connectionID = this.connectionDetailList.size();
						/*
						 * Store the connection ID to Word 1.
						 */
						this.simHW.store(Hardware.Address.systemBase + 1, connectionID);
						/*
						 * Get and set the connection ID
						 */
						println("[[[[[[[[[[[[[[[[[[[[[[ osCall_open.connectionID: (Connection ID): " + connectionID + " ]]]]]]]]]]]]]]]]]]]]]]");
						cDetail.setConnectionID(connectionID);
						/*
						 * Connection was used to open it.
						 */
						cDetail.setConnectionOpened(true); 
						cDetail.setDeviceType(deviceType);						
						/*
						 * Save connection id after the call within the list.
						 */
						connectionDetailList.add(cDetail);
						
					} else if (getDeviceStatus == Hardware.Status.badDevice) {
						this.setDeviceStatus(Hardware.Status.badDevice);
						this.setUndefinedConnectionId();						
					}
				} else if (hwStatus == Hardware.Status.deviceBusy) {
					this.setDeviceStatus(Hardware.Status.deviceBusy);
					this.setUndefinedConnectionId();
				} else if (hwStatus == Hardware.Status.badDevice) {
					this.setDeviceStatus(Hardware.Status.badDevice);
					this.setUndefinedConnectionId();
				}
				
			} else if (deviceType == Hardware.Terminal.device) {
				terminalInUse = terminalUseStack.contains(deviceType);
				println("terminalInUse::::::" + terminalInUse);				/*
				 * Triggers the call.
				 */
				this.setHardwareStatusOK();	
				int terminalHWStatus = this.simHW.fetch(Hardware.Address.systemBase);
				if (terminalHWStatus == Hardware.Status.ok) {					
					if (terminalInUse == false) {
						ConnectionDetails cnDetail = new ConnectionDetails();
						int connectionID = this.connectionDetailList.size();
						println("osCall_Open.connectionID (Size): " + connectionID);
						/*
						 * Set the connection ID.
						 */
						cnDetail.setConnectionID(connectionID);
						/*
						 * Connection was used to open it.
						 */
						cnDetail.setConnectionOpened(true);
						/*
						 * Store the connection type.
						 */
						cnDetail.setDeviceType(deviceType);
						/*
						 * Save connection id after the call within the list.
						 */
						connectionDetailList.add(cnDetail);
						/*
						 * Set the terminal in use status.
						 */
						terminalUseStack.push(deviceType);
					} else if (terminalInUse) {
						println("Terminal is already open...");
						this.setDeviceStatus(Hardware.Status.deviceBusy);
					}					
				} else if (terminalHWStatus == Hardware.Status.deviceBusy) {
					this.setDeviceStatus(Hardware.Status.deviceBusy);
				} else if (terminalHWStatus == Hardware.Status.badDevice) {
					this.setDeviceStatus(Hardware.Status.badDevice);
				} else {
					this.setDeviceStatus(-1);
				}
			}
		}
	}
	
	/**
	 * OS Call Read
	 */
	public void osCall_Read() {
		int readConnectionID = this.simHW.fetch(Hardware.Address.systemBase + 1); 
		int beforeCallReadReadToAddress = this.simHW.fetch(Hardware.Address.systemBase + 2) + 1;
		int beforeCallRead_N_Value = this.simHW.fetch(Hardware.Address.systemBase + 3);
		
		/*
		 * This is what will trigger the call.
		 */
		this.setHardwareStatusOK();

		/*
		 * Get the connection info based on the details.
		 */
		int cnnDetailListSize = connectionDetailList.size();
		println("cnnDetailListSize: " + cnnDetailListSize);
		println("readConnectionID: " + readConnectionID);
		
		if (readConnectionID < cnnDetailListSize && readConnectionID >= 0) {
			
			int deviceReadStatus = this.simHW.fetch(Hardware.Address.systemBase);
			if (deviceReadStatus == Hardware.Status.ok) {			
				
				if (beforeCallRead_N_Value >= 0) {
					if (beforeCallReadReadToAddress >= 0) {
						println("Read to block: " + beforeCallRead_N_Value);
						println("Read to address: " + beforeCallReadReadToAddress);
						println("Read total memoryManager.getTotalDiskBlocskUsed(): " + memoryManager.getTotalDiskBlocskUsed());
						
						ConnectionDetails cnnDetails = connectionDetailList.get(readConnectionID);
						cnnDetails.setReadTo_N_Value(beforeCallRead_N_Value);
						cnnDetails.setReadToAddress(beforeCallReadReadToAddress);
						cnnDetails.setReadyRead(true);
						connectionDetailList.add(cnnDetails);
						
						this.readDiskBlockIntoPrimaryStore(beforeCallRead_N_Value, beforeCallReadReadToAddress);
						println("");							
					} else {
						this.setDeviceStatus(Hardware.Status.badAddress);
					}
				} else {
					this.setDeviceStatus(Hardware.Status.badCount);
				}	
				/*
				 * The disk read operation is ready.
				 */
				this.readyRead = true;

			} else if (deviceReadStatus == Hardware.Status.badDevice) {
				this.setDeviceStatus(Hardware.Status.badDevice);
			} else if (deviceReadStatus == Hardware.Status.badAddress) {
				this.setDeviceStatus(Hardware.Status.badAddress);
			} else if (deviceReadStatus == Hardware.Status.badCount) {
				this.setDeviceStatus(Hardware.Status.badCount);
			}
		} else {
			this.setDeviceStatus(Hardware.Status.badDevice);
		}
	}
	
	/**
	 * OS Call Write
	 */
	public void osCall_Write() {
		int beforeWriteConnectionID = this.simHW.fetch(Hardware.Address.systemBase + 1);
		int beforeWriteWriteFromAddress = this.simHW.fetch(Hardware.Address.systemBase + 2);
		int beforeWrite_N_Value = this.simHW.fetch(Hardware.Address.systemBase + 3);
		
		int cnnDetailListSize = connectionDetailList.size();
		if (beforeWriteConnectionID < cnnDetailListSize && beforeWriteConnectionID >= 0) {
			
			
			/*
			 * This is what will trigger the call.
			 */
			this.setHardwareStatusOK();
			int hwStatus = this.simHW.fetch(Hardware.Address.systemBase);						
			if (hwStatus == Hardware.Status.ok) {
								
				if (beforeWrite_N_Value >= 0) {
					if (beforeWriteWriteFromAddress >= 0) {
						println("Hardware.Disk.device: Hardware.Status.ok");
						ConnectionDetails cnnDetails = this.connectionDetailList.get(beforeWriteConnectionID);
						
						cnnDetails.setWriteFrom_N_Value(beforeWrite_N_Value);			
						cnnDetails.setWriteFromAddress(beforeWriteWriteFromAddress);
						println("beforeWrite_N_Value: " + beforeWrite_N_Value);
						println("beforeWriteWriteFromAddress: " + beforeWriteWriteFromAddress);
						println("");
						/*
						 * Connection is ready to write.
						 */				
						cnnDetails.setReadyWrite(true);				
						/*
						 * Save the write connection information.
						 */
						this.connectionDetailList.set(beforeWriteConnectionID, cnnDetails);				
						
						if (cnnDetails.getDeviceType() == Hardware.Disk.device) {				
							this.writeDiskBlockIntoPrimaryStore(beforeWrite_N_Value, beforeWriteWriteFromAddress);				 
						}						
						this.readyWrite = true;												
					} else {
						this.setDeviceStatus(Hardware.Status.badAddress);
					}
				} else {
					this.setDeviceStatus(Hardware.Status.badCount);
				}				
			} else if (hwStatus == Hardware.Status.badDevice) {
				this.setDeviceStatus(Hardware.Status.badDevice);
			} else if (hwStatus == Hardware.Status.badAddress) {
				this.setDeviceStatus(Hardware.Status.badAddress);
			} else if (hwStatus == Hardware.Status.badCount) {
				this.setDeviceStatus(Hardware.Status.badCount);
			}	
		} else {
			this.setDeviceStatus(Hardware.Status.badDevice);
		}			
	}
	
	/**
	 * OS Call Close
	 */
	public void osCall_Close() {
		int closeConnectionID = this.simHW.fetch(Hardware.Address.systemBase + 1); // Word 1 (1 is drive, 3 is terminal)
		println("====>>>>>>>>> osCall_Close.closeConnectionID: (closeConnectionID) " + closeConnectionID);
		
		/*
		 * This is what will trigger the call.
		 */
		this.setHardwareStatusOK();
		/* 
		 * Get the device status.
		 */
		int deviceStatus = this.simHW.fetch(Hardware.Address.systemBase);
		println("deviceStatus: " + deviceStatus);
		if (deviceStatus == Hardware.Status.ok) {			
			int cnnDetailListSize = connectionDetailList.size();
			if (closeConnectionID < cnnDetailListSize && closeConnectionID >= 0) {				
				ConnectionDetails cnDetail = this.connectionDetailList.get(closeConnectionID);
				if (cnDetail.isConnectionClosed() == false) {
					int deviceType = cnDetail.getDeviceType();
					if (deviceType == Hardware.Disk.device) {						
						/*
						 * Set the connection status to closed.
						 */
						cnDetail.setConnectionClosed(true);
						println("[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ osCall_Close.closeConnectionID: (Connection ID)" + closeConnectionID + " ]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]");
						/*
						 * Set hardware status to OK.
						 */
						this.setHardwareStatusOK();					
					} else if (deviceType == Hardware.Terminal.device) {
						/*
						 * Is the terminal being used for it to be closed?
						 */
						terminalInUse = terminalUseStack.contains(deviceType);
						println("Terminal in use?: " + terminalInUse);
						if (terminalInUse) {
							/*
							 *  Pop the terminal stack to allow for it to be used again.
							 */
							terminalUseStack.pop(); 
							/*
							 * Set the connection status to closed.
							 */
							cnDetail.setConnectionClosed(true);
							println("[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ osCall_Close.closeConnectionID: (Connection ID)" + closeConnectionID + " ]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]");
							/*
							 * Set hardware status to OK.
							 */
							this.setHardwareStatusOK();						
						} else {
							println("Terminal already closed...");
							this.setDeviceStatus(Hardware.Status.badDevice);
							this.setUndefinedConnectionId();
						}
					}
				} else {
					println("Device already closed...");
					this.setDeviceStatus(Hardware.Status.badDevice);
					this.setUndefinedConnectionId();
				}				
			} else {
				println("This is a bad device ID...");
				this.setDeviceStatus(Hardware.Status.badDevice);
				this.setUndefinedConnectionId();
				
			}
		} else if (deviceStatus == Hardware.Status.badDevice) {
			this.setDeviceStatus(Hardware.Status.badDevice);
			this.setUndefinedConnectionId();
		}		
	}
	
	/**
	 * Initializes OS
	 */
	private void initializeOperatingSystem() {
		int exeProgramBlockCount = getExeProgsBlockCount();  // total programs block count.
		this.println("exeProgramBlockCount::" + exeProgramBlockCount);
		if(exeProgramBlockCount == 0) { this.haltOS(); }				
		
		int totalBlockCount = exeProgramBlockCount + 1;			
		this.println("totalBlockCount::" + totalBlockCount);
		int nextBlkIndex = memoryManager.getBlockIndex() + 1;		
		/*
		 * Index block is loaded on boot.  Load the program blocks by counting blocks in the index block.
		 */
		if (nextBlkIndex < totalBlockCount) {
			int nextBlockIndex = this.memoryManager.getBlockIndex() + 1;
			int nextBlockStartAddress = simHW.fetch(Hardware.Address.diskAddressRegister) + 32;
			this.memoryManager.setBlockIndex(nextBlockIndex);
			this.memoryManager.setBlockStartAddress(nextBlockStartAddress);
			
			println("nextBlockIndex::" + nextBlockIndex);
			println("nextBlockStartAddress::" + nextBlockStartAddress);
			
			this.readDiskBlockIntoPrimaryStore(nextBlockIndex, nextBlockStartAddress);			
			
			println("nextBlockIndex::" + nextBlockIndex);
			println("exeProgramBlockCount::" + exeProgramBlockCount);					
		} 
				
		/*
		 * Program entity loading here.  Used to request program execution.
		 */
		if (nextBlkIndex == totalBlockCount && memoryManager.isProgramLoadingComplete() == false){				
			memoryManager.setProgramLoadingComplete(true);
			println("Info:isProgramLoadingComplete: " + memoryManager.isProgramLoadingComplete());
			memoryManager.setProgramLoadingComplete(true);
			this.createDiskEntity();
			List<WordEntity> iEntity = dEnt.getBlockEntity(0).getWordEntityList();
			this.queueProcessExecution(iEntity);
			/*
			 * Must only run once.
			 */
			this.setCountdownStart(countdown);	
			this.setIdleStart();		
		} 

		if (memoryManager.isProgramLoadingComplete() == true && memoryManager.isFirstProgramStarted() == false) {				
			println("First program started...");
			/*
			 * All index and program blocks loaded.  It is time to execute the first program.
			 */
			if (memoryManager.isFirstProgramStarted() == false) {
				println("Is Program Started?????" + memoryManager.isFirstProgramStarted());					
				int proIndex = 0;
				processProgramExeRequest(proIndex); 
				memoryManager.setFirstProgramStarted(true);
				println("First program executed...");
				memoryManager.setOperatingSystemInitialized(true);				
				this.setCountdownStart(countdown);
			}				
		}		
	}
	
	/**
	 * Loads a disk block into primary store.
	 * @param blockIndex
	 * @param blockStartAddress
	 */
	private void readDiskBlockIntoPrimaryStore(int blockIndex, int blockStartAddress) {
		println("readDiskBlockIntoPrimaryStory.blockIndex: " + blockIndex);
		println("readDiskBlockIntoPrimaryStory.blockStartAddress: " + blockStartAddress);
		
		this.simHW.store(Hardware.Address.diskBlockRegister, blockIndex);   			
		this.simHW.store(Hardware.Address.diskAddressRegister, blockStartAddress);			
		this.simHW.store(Hardware.Address.diskCommandRegister, Hardware.Disk.readCommand);		
	}	
	
	private void writeDiskBlockIntoPrimaryStore(int blockIndex, int blockStartAddress) {		
		println("writeDiskBlockIntoPrimaryStory.blockIndex: " + blockIndex);
		println("writeDiskBlockIntoPrimaryStory.blockStartAddress: " + blockStartAddress);
		
		this.simHW.store(Hardware.Address.diskBlockRegister, blockIndex);//Next block from disk   			
		this.simHW.store(Hardware.Address.diskAddressRegister, blockStartAddress);//Set next block start address			
		this.simHW.store(Hardware.Address.diskCommandRegister, Hardware.Disk.writeCommand);									
	}	
	
	/**
	 * Process control block scheduling.
	 */
	private void executeProcessScheduling() {
		do {
			if (processQueue.size() > 0) {
				clock++;
				processScheduler.T = processScheduler.processready(clock);
				if (processScheduler.T != null) {
					processScheduler.readyQueue.add(processScheduler.T);
					processScheduler.queue.remove(processScheduler.T);
					println("Time " + processScheduler.T.getArrival() + ":Process " + processScheduler.T.getProcessId() + " ready.");

				} // put in ready queue
				if (processScheduler.idle || processScheduler.interrupt) {
					if (processScheduler.interrupt) {
						processScheduler.interrupt = false;
						processScheduler.interval = 0;
					} // reset interval
					if (processScheduler.readyQueue.size() == 0)
						continue;
					if (processScheduler.next < processScheduler.readyQueue.size() - 1)
						processScheduler.next++;
					else
						processScheduler.next = 0;
						processScheduler.P = processScheduler.readyQueue.get(processScheduler.next);
						processScheduler.idle = false;
				} // get next process
				processScheduler.P.servicing();

				/*
				 * Set the program using program ID.
				 */
				println("processScheduler.P.getParentProgramID()" + processScheduler.P.getProcessId());
				int processID = processScheduler.P.getProcessId();
				this.processProgramExeRequest(processID);				
				processScheduler.interval++;

				println("Time " + clock + ":Serving process " + processScheduler.P.getProcessId() + ".");

				if (processScheduler.P.getTminus() == 0) {
					println("Time " + (clock + 1) + ":Process " + processScheduler.P.getProcessId() + " done.");

					this.resetCountdown();
					this.setCountdownStart(countdown);

					processScheduler.P.report(clock + 1); // anticipate
															// completion
					processScheduler.finishQueue.add(processScheduler.P);
					processScheduler.readyQueue.remove(processScheduler.P);
					processScheduler.next--; // recent pointer.
					processScheduler.idle = true;
					processScheduler.interval = 0;
				} // put in finish queue
				else if (processScheduler.interval == processQueue.size()) {
					processScheduler.interrupt = true;
				}
			}
		} while (processScheduler.finishQueue.size() < processScheduler.all);
	}
		
	/**
	 * Get the total program block count.
	 * @return
	 */
	private int getExeProgsBlockCount(){
		int blkCount = 0;		
		for ( int i = 0; i < Hardware.Disk.blockSize; i++ ){
			int programBlocks = simHW.fetch(Hardware.Address.userBase + i);//Find how many blocks executable programs occupy.
			if (programBlocks != 0){
				println("Process ID: [" + i +"]; Block Count: [" + programBlocks + "]");
				blkCount += programBlocks;
			}						
		}		
		return blkCount;
	}
	
	/**
	 * Get the total program block count.
	 * @return
	 */
//	private int getExeProgramCount(){
//		int count = 0;		
//		for ( int i = 0; i < Hardware.Disk.blockSize; i++ ){
//			int programBlocks = simHW.fetch(Hardware.Address.userBase + i);
//			if (programBlocks != 0){
//				count++; 				
//			}						
//		}		
//		return count;
//	}	
			
	/**
	 * Creates the disk blocks to reference for referencing program block information
	 */
	private void createDiskEntity(){		
		List<WordEntity> wordEnt = new ArrayList<WordEntity>();
		
		for (int i = Hardware.Address.userBase; i < Hardware.Address.userTop - 1; i++) 
		{
			WordEntity wEnt = new WordEntity();				
			wEnt.setWordAddress(i);
			wordEnt.add(wEnt);			
		}			
		
		List<WordEntity> wordList = Collections.unmodifiableList(wordEnt);
		List<List<WordEntity>> subParts = createBlockList(wordList, 32);
		
		for (int i = 0; i < subParts.size(); i++)
		{
			BlockEntity blkEnt = new BlockEntity();
			blkEnt.setWordEntityList(subParts.get(i));
			dEnt.getBlockEntityList().add(blkEnt);
		}				
	}
	
	/**
	 * Create block list.
	 * @param list
	 * @param L
	 * @return
	 */
	private static <T> List<List<T>> createBlockList(List<T> list, final int L) {
		List<List<T>> parts = new ArrayList<List<T>>();
		final int N = list.size();
		for (int i = 0; i < N; i += L) {
			parts.add(new ArrayList<T>(list.subList(i, Math.min(N, i + L))));
		}
		return parts;
	}

	/**
	 * Shows how to get to the first and last block of each process based on the index block(0).
	 * @param nBlock
	 */
	private void queueProcessExecution(List<WordEntity> iBlock){
		int firstBlockIndex = 0;
		int lastBlockIndex = 0;
		int baseRegister = 0;
		int topRegister = 0;
		int pCRegister = 0;
		for (int i = 0; i < iBlock.size(); i++){
			int proAddr = iBlock.get(i).getWordAddress(); 
			int processBlockCount = this.simHW.fetch(proAddr); 			
			if (processBlockCount != 0) {
				if (i == 0) {
					firstBlockIndex = 1;
					lastBlockIndex = firstBlockIndex + processBlockCount;
					int processStartBlockAddress = dEnt.getBlockEntityList().get(firstBlockIndex).getWordEntityList().get(0).getWordAddress(); 
					int processEndBlockAddress = dEnt.getBlockEntityList().get(lastBlockIndex).getWordEntityList().get(0).getWordAddress(); 
				
					/*
					 *  The hardware interprets the contents of the Base Register 
					 *  as the lowest legal address accessible to the currently running process.
					 */
					baseRegister = processStartBlockAddress;  
					
					/*
					 * The hardware interprets the contents of the Top Register 
					 * as one more than the highest legal address accessible to the currently running process.
					 */
					topRegister = processEndBlockAddress + 1;
					
					/*
					 *  The hardware interprets the contents of the Program Counter Register 
					 *  as the address of the next instruction to execute.
					 */
					pCRegister = processStartBlockAddress;  
									
					println("queueProcessExecution.pCRegister: " + pCRegister);
					println("queueProcessExecution.baseRegister: " + baseRegister);
					println("queueProcessExecution.topRegister: " + topRegister);
					println("");
									
					/*
					 * Set the program block list to allow to iterate using a preemptive round robin scheduling scheme base on a count down.
					 */
					this.setProgramBlockList(processStartBlockAddress, processBlockCount, pCRegister, baseRegister, topRegister);					

				} else {
					firstBlockIndex += lastBlockIndex - 1;
					lastBlockIndex = firstBlockIndex + processBlockCount;
					int processStartBlockAddress = dEnt.getBlockEntityList().get(firstBlockIndex).getWordEntityList().get(0).getWordAddress(); 
					int processEndBlockAddress = dEnt.getBlockEntityList().get(lastBlockIndex).getWordEntityList().get(0).getWordAddress(); 
				
					/*
					 *  The hardware interprets the contents of the Base Register 
					 *  as the lowest legal address accessible to the currently running process.
					 */
					baseRegister = processStartBlockAddress;  
					
					/*
					 * The hardware interprets the contents of the Top Register 
					 * as one more than the highest legal address accessible to the currently running process.
					 */
					topRegister = processEndBlockAddress + 1;
					
					/*
					 *  The hardware interprets the contents of the Program Counter Register 
					 *  as the address of the next instruction to execute.
					 */
					pCRegister = processStartBlockAddress;  
									
					println("queueProcessExecution.pCRegister: " + pCRegister);
					println("queueProcessExecution.baseRegister: " + baseRegister);
					println("queueProcessExecution.topRegister: " + topRegister);
					println("");
									
					/*
					 * Set the program block list to allow to iterate using a preemptive round robin scheduling scheme base on a count down.
					 */
					this.setProgramBlockList(processStartBlockAddress, processBlockCount, pCRegister, baseRegister, topRegister);					

				}				
			}			
		}				
	}
	
	/**
	 * Creates a list containing all of the blocks within one program.  This allows preemptive round robin execution.
	 * @param programStartBlock
	 * @param processBlockCount
	 * @param pCRegister
	 * @param baseRegister
	 * @param topRegister
	 */
	private void setProgramBlockList(int programStartBlock, int processBlockCount, int pCRegister, int baseRegister, int topRegister){
		BlockEntity bEnt = new BlockEntity();
		for (int proBlockIndex = programStartBlock; proBlockIndex <= processBlockCount; proBlockIndex++){ // Go through the addresses in block range.
			List<WordEntity> iBlock = dEnt.getBlockEntityList().get(proBlockIndex).getWordEntityList(); // Get the block range based on start and end blocks.
			for (int j = 0; j < iBlock.size(); j ++) {
				bEnt.getWordEntityList().add(iBlock.get(j));				
			}			
		}
		bEnt.setBaseRegister(baseRegister); 
		bEnt.setTopRegister(topRegister);
		bEnt.setPCRegister(pCRegister);
		proEnt.getBlockEntityList().add(bEnt);
	}
	
	/**
	 * Process a program based on the range found in a program.
	 * @param index
	 */
	private void processProgramExeRequest(int index) {
		int progCount = this.proEnt.getBlockEntityList().size();
		if (index < progCount){
			
			println("Program Index: " + index);
			
			int pCRegister = proEnt.getBlockEntityList().get(index).getPCRegister();
			int baseRegister = proEnt.getBlockEntityList().get(index).getBaseRegister();
			int topRegister = proEnt.getBlockEntityList().get(index).getTopRegister();
			
			println("pCRegister: " + pCRegister);
			println("baseRegister: " + baseRegister);
			println("topRegister: " + topRegister);
			
			this.simHW.store(Hardware.Address.baseRegister, baseRegister); // Lowest legal address accessible to the current running process.
			this.simHW.store(Hardware.Address.topRegister, topRegister); // One more than the highest legal address.
			this.simHW.store(Hardware.Address.PCRegister, pCRegister); // Address of the next program to execute.			
			
		} else {
			println("Program Index is not valid...");
			simHW.store(Hardware.Address.haltRegister, 2);
		}			
	}
	
	private void setDeviceStatus(int status){
		println("deviceReadStatus: " + status);
		this.simHW.store(Hardware.Address.systemBase, status);
	}	
		
	private void println(Object msg){
		System.out.println(msg);		
	}
}